import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/Addons.js'
import Stats from 'three/examples/jsm/libs/stats.module.js'
import * as dat from 'dat.gui'
import { Font, FontLoader, TextGeometry, TextGeometryParameters } from "three/examples/jsm/Addons.js"
import Utils from '../utils'
import ObjectGenerator from './ObjectGenerator'
import Transformer from './Transformer'

export interface ViewerOptions {
  width?: number
  height?: number
  orthographic?: boolean
}

class Grid {
  declare scene: THREE.Scene
  declare font: Font
  declare plane: THREE.Mesh
  declare axes: THREE.Object3D
  constructor(scene: THREE.Scene, font: Font) {
    this.scene = scene
    this.font = font
    this.createGrid(40, 40, 0x000000)
  }

  // 创建网格
  createGrid(size: number, divisions: number, color: number) {
    this.axes = this.createAxis(this.font)
    this.axes.position.y = 0.05
    this.plane = this.createPlane(size, size, color)
    this.scene.add(this.axes, this.plane)
  }
  // 创建坐标轴
  createAxis(font: Font, axisLength = 20, step = 5, fontSize = 0.5) {
    const axes = new THREE.Object3D();
    const axesArray = ['x', 'y', 'z']
    const tickLength = 0.2

    // 坐标轴
    axesArray.forEach(dir => {
      // red, green, blue
      const colorStr = dir === 'x' ? '1, 0.6, 0' : dir === 'y' ? '0.6, 1, 0' : '0, 0.6, 1'
      const color = new THREE.Color(...colorStr.split(','))

      const dirVector = new THREE.Vector3();
      dirVector[dir === 'x' ? 'x' : dir === 'y' ? 'y' : 'z'] = axisLength;

      const arrow = new THREE.ArrowHelper(dirVector, dirVector.clone().multiplyScalar(-1), axisLength * 2, color, 0.5, 0.5);
      axes.add(arrow)
    });

    // 刻度和标签
    const tickGeometry = new THREE.BufferGeometry();

    axesArray.forEach(dir => {

      for (let i = -axisLength; i <= axisLength; i += step) {
        if (i !== 0) {
          // 刻度线
          const tickGeom = tickGeometry.clone();
          const pos = new THREE.Vector3(dir === 'x' ? i : 0, dir === 'y' ? i : 0, dir === 'z' ? i : 0);
          const dirVec = new THREE.Vector3(dir === 'x' ? 1 : 0, dir === 'y' ? 1 : 0, dir === 'z' ? 1 : 0);
          tickGeom.translate(pos.x, pos.y, pos.z);
          tickGeom.lookAt(dirVec);
          tickGeom.translate(0, 0, -tickLength / 2); // tickLength 可自定义

          tickGeom.setFromPoints([pos, pos.clone().add(dirVec.multiplyScalar(tickLength))])
          const line = new THREE.Line(tickGeom, new THREE.LineBasicMaterial({ color: 0x000000 }));
          axes.add(line);

          // 标签
          if (Math.abs(i) % step === 0) {
            const textGeo = new TextGeometry(i.toString(), {
              font: font,
              size: fontSize,
              height: 0.01,
            });

            const textMesh = new THREE.Mesh(textGeo, new THREE.MeshBasicMaterial({ color: 0x000000 }));
            textMesh.position.copy(pos);
            axes.add(textMesh);
          }
        }
      }
    });

    return axes;
  }
  // 创建平面
  createPlane(width: number, height: number, color: number) {
    const planeGeometry = new THREE.PlaneGeometry(width, height)
    const planeMaterial = new THREE.ShaderMaterial({
      transparent: true,
      side: THREE.DoubleSide,
      uniforms: {
        color: { value: new THREE.Color(color) },
        opacity: { value: 0.5 },
      },
      vertexShader: `
      varying vec2 vUv;
        void main() {
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
          vUv = uv;
        }`,
      fragmentShader: `
        uniform vec3 color;
        uniform float opacity;
        varying vec2 vUv;
        void main() {
          float alpha = smoothstep(0.0,1.0,opacity);
          alpha *= smoothstep(0.0,0.1,vUv.x);
          alpha *= smoothstep(1.0,0.9,vUv.x);
          alpha *= smoothstep(0.0,0.1,vUv.y);
          alpha *= smoothstep(1.0,0.9,vUv.y);

          gl_FragColor = vec4(color,alpha);
        }`
    })
    const plane = new THREE.Mesh(planeGeometry, planeMaterial)
    plane.rotation.x = -Math.PI / 2
    return plane
  }
}

export default class Viewer extends THREE.EventDispatcher<any> {
  declare public width: number
  declare public height: number
  declare public dom: HTMLElement

  declare scene: THREE.Scene
  declare camera: THREE.PerspectiveCamera | THREE.OrthographicCamera
  declare renderer: THREE.WebGLRenderer
  declare controls: OrbitControls | null
  declare clock: THREE.Clock
  declare ambientLight: THREE.AmbientLight
  declare stats: Stats
  declare gui: dat.GUI
  declare raycaster: THREE.Raycaster

  declare font: Font
  declare frame: number
  declare loading: boolean
  declare grid: Grid
  declare transformer: Transformer
  declare objectGenerator: ObjectGenerator

  resizeFn!: () => void

  constructor(dom: HTMLElement, options?: ViewerOptions) {
    super()
    this.dom = dom
    this.width = options?.width || 800
    this.height = options?.height || 600

    this.scene = new THREE.Scene()
    if (options?.orthographic) {
      this.camera = new THREE.OrthographicCamera(-this.width / 50, this.width / 50, this.height / 50, -this.height / 50, 0.1, 2000)
    } else {
      this.camera = new THREE.PerspectiveCamera(75, this.width / this.height, 0.1, 2000)
    }
    this.camera.position.set(-10, 10, 30)

    this.renderer = new THREE.WebGLRenderer()
    this.renderer.setSize(this.width, this.height)
    this.renderer.setPixelRatio(window.devicePixelRatio)
    this.renderer.setClearColor(0xffffff, 1)
    this.renderer.render(this.scene, this.camera)
    this.dom.appendChild(this.renderer.domElement)

    this.ambientLight = new THREE.AmbientLight(0xffffff, 0.1)
    this.scene.add(this.ambientLight)

    this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    this.controls.enableDamping = true

    this.clock = new THREE.Clock()

    this.stats = new Stats()
    this.dom.appendChild(this.stats.dom)

    this.gui = new dat.GUI()

    this.frame = window.requestAnimationFrame(this.tick.bind(this))
    this.raycaster = new THREE.Raycaster()
  }
  async init() {
    try {
      this.loading = true
      // 加载font文件
      this.font = await new FontLoader().loadAsync('/fonts/optimer_bold.typeface.json')
      this.resizeFn = this.onResize.bind(this)
      window.addEventListener('resize', this.resizeFn)
      this.test()
      this.grid = new Grid(this.scene, this.font)
      this.objectGenerator = new ObjectGenerator(this)
      this.transformer = new Transformer(this)
      this.dispatchEvent('ready')
    } finally {
      this.loading = false
    }
  }
  destroy() {
    window.removeEventListener('resize', this.resizeFn)
    this.gui.destroy()
    this.renderer?.domElement.remove()
    this.stats?.dom.remove()
    this.dispatchEvent('destroy')
  }
  onResize() {
    this.dispatchEvent('resize')
    this.renderer.setSize(innerWidth, innerHeight)
    if (this.camera instanceof THREE.PerspectiveCamera) {

      this.camera.aspect = innerWidth / innerHeight
      this.camera.updateProjectionMatrix()
    }
  }
  tick() {
    this.frame = window.requestAnimationFrame(this.tick.bind(this))

    this.stats.update()

    this.renderer.render(this.scene, this.camera)
    this.controls?.update()
  }
  test() {
    const geometry = new THREE.BoxGeometry(5, 5, 5)
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
    const cube = new THREE.Mesh(geometry, material)
    cube.position.y = 5
    this.scene.add(cube)
  }
  cText(content: string, target?: THREE.Object3D | THREE.Vector3, options?: TextGeometryParameters) {
    const params = Object.assign({ font: this.font, size: 0.5, height: 0.01 }, options)
    const geometry = new TextGeometry(content, params)
    const material = new THREE.MeshBasicMaterial({ color: Utils.getRandomColor() })
    const mesh = new THREE.Mesh(geometry, material)
    this.scene.add(mesh)

    if (target) {
      if (target instanceof THREE.Object3D) {
        mesh.position.copy(target.position)
        target.add(mesh)
      } else {
        mesh.position.copy(target)
      }
    }

    return mesh
  }
}